package com.startx.core.netty.handler;

import static com.startx.core.system.HttpParser.getHttpBody;
import static com.startx.core.system.HttpParser.getHttpForm;
import static com.startx.core.system.HttpParser.getHttpHeader;
import static com.startx.core.system.HttpParser.getHttpParam;
import static com.startx.core.system.HttpParser.getURI;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URLDecoder;
import java.util.Date;
import java.util.Objects;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.springframework.util.StringUtils;

import com.startx.core.config.ConfigHolder;
import com.startx.core.filter.chain.FilterFactory;
import com.startx.core.mvc.define.BodyType;
import com.startx.core.mvc.factory.MappingFactory;
import com.startx.core.netty.response.http.CommonResponse;
import com.startx.core.netty.response.http.JsonResponse;
import com.startx.core.netty.response.http.ResourceResponse;
import com.startx.core.netty.response.http.XmlResponse;
import com.startx.core.system.HttpParser;
import com.startx.core.system.config.StartxConfig;
import com.startx.core.system.constants.Constants;
import com.startx.core.system.constants.Headers;
import com.startx.core.system.model.AccessResult;
import com.startx.core.system.model.AccessTarget;
import com.startx.core.system.param.HttpBody;
import com.startx.core.system.param.HttpContext;
import com.startx.core.system.param.HttpForm;
import com.startx.core.system.param.HttpHeader;
import com.startx.core.system.param.HttpParam;
import com.startx.core.tools.DateUtil;
import com.startx.core.tools.GZipUtils;
import com.startx.core.tools.MimeType;

import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponseStatus;

@Sharable
public class HttpHandler extends ChannelInboundHandlerAdapter {

	private static final Logger Log = Logger.getLogger(HttpHandler.class);
	private StartxConfig config = ConfigHolder.getConfig();

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		FullHttpRequest req = (FullHttpRequest) msg;
//		new HttpHandler().
		handleHttpRequest(ctx, req);
	}

	/**
	 * 解析http请求
	 */
	public void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {

		if (request.decoderResult().isFailure()) {
			Log.error("一次奇怪的访问,IP:[" + ctx.channel().remoteAddress() + "]... ");
			ctx.close();
			return;
		}
		
		String uri = URLDecoder.decode(getURI(request),"utf-8");
		
		// GET POST ...
		AccessTarget target = getTarget(ctx, request, uri);

		if (Objects.isNull(target)) {
			HttpHeader headers = getHttpHeader(request);
			writeResource(ctx, uri,headers);
			return;
		}
		
		//获取Http上下文
		HttpContext context = HttpParser.getHttpContext(ctx, request);
		
		if(FilterFactory.filterable() && !FilterFactory.doBefore(context, request,target)) {
			if (ctx.channel().isOpen()) {
				FullHttpResponse response = CommonResponse.getResponse(HttpResponseStatus.SERVICE_UNAVAILABLE);
				ChannelFuture f = ctx.channel().writeAndFlush(response);
				f.addListener(ChannelFutureListener.CLOSE);
			}
			return;
		}
		
		BodyType bodyType = target.getType();
		//
		Method method = target.getMethod();
		Parameter[] paramType = method.getParameters();
		Object[] paramValue = new Object[paramType.length];

		// 设置方法调用参数，并调用参数返回，返回之前调用过滤器
		{
			setParams(context, request, bodyType, paramType, paramValue);
			// 调用方法
			Object result = method.invoke(target.getObj(), paramValue);
			// 根据类型写返回值
			ifReturnValueThenOutput(context, bodyType, new AccessResult(target,result));
			// 检测链接是否关闭
			ifIsOpenThenClose(context.getContext());
		}
	}

	/**
	 * 写入静态内容
	 * @param ctx
	 * @param uri
	 * @throws IOException
	 */
	private void writeResource(ChannelHandlerContext ctx, String uri, HttpHeader headers) throws IOException {
		if(StringUtils.isEmpty(uri)) {
			FullHttpResponse response = CommonResponse.getResponse(HttpResponseStatus.NOT_FOUND);
			ChannelFuture f = ctx.channel().writeAndFlush(response);
			f.addListener(ChannelFutureListener.CLOSE);
			return;
		}
		
		if(uri.lastIndexOf(Constants.DOT) == -1) {
			uri = uri + config.getPageIndex();
		}
		
		//获取默认静态目录
		String webRoot = config.getWebRoot();
		File file = new File(webRoot+uri);
		
		if(!file.exists()) {
			FullHttpResponse response = CommonResponse.getResponse(HttpResponseStatus.NOT_FOUND);
			ChannelFuture f = ctx.channel().writeAndFlush(response);
			f.addListener(ChannelFutureListener.CLOSE);
			return;
		}
		
		String modifyDate = headers.getHeader("If-Modified-Since");
		String lastModify = DateUtil.formatRfc822Date(new Date(file.lastModified()));
		
		if(lastModify.equals(modifyDate)) {
			HttpHeaders responseHeaders = new DefaultHttpHeaders();
			responseHeaders.add("Last-Modified",lastModify);
			responseHeaders.add("Date",DateUtil.formatRfc822Date(new Date()));
			/////////////////////////////////////////////////////////////////////
			FullHttpResponse response = 
					ResourceResponse.getResponse(HttpResponseStatus.NOT_MODIFIED,responseHeaders);
			ChannelFuture f = ctx.channel().writeAndFlush(response);
			f.addListener(ChannelFutureListener.CLOSE);
			return;
		}
		
		String defaultAcceptEncoding = headers.getHeader(Headers.Values.AcceptEncoding);
		
		writeResource(ctx, uri, file, lastModify, defaultAcceptEncoding);
		
		//
	}

	/**
	 * 返回数据
	 */
	private void writeResource(ChannelHandlerContext ctx, String uri, File file, 
			String lastModify,String defaultAcceptEncoding)throws FileNotFoundException, IOException {
		//处理静态文件
		FileInputStream input = new FileInputStream(file);
		
		ByteArrayOutputStream output = new ByteArrayOutputStream();
		IOUtils.copy(input, output);
		
		String mimeType = MimeType.getMimeType(FilenameUtils.getExtension(uri));
		//set header
		HttpHeaders responseHeaders = new DefaultHttpHeaders();
		responseHeaders.add("Date",DateUtil.formatRfc822Date(new Date()));
		responseHeaders.add("Last-Modified",lastModify);
		
		
		if(!Objects.isNull(mimeType)) {
			responseHeaders.add("Content-Type",mimeType);
		}
		
		FullHttpResponse response;
		byte[] data;
		if(Headers.Values.DefaultAcceptEncoding.equals(defaultAcceptEncoding)) {
			data = output.toByteArray();
			responseHeaders.add("Content-Encoding", Headers.Values.DefaultAcceptEncoding);
		} else {
			data = GZipUtils.compress(output.toByteArray());
			responseHeaders.add("Content-Encoding", "gzip");
		}
		responseHeaders.add("Content-Length",file.length());
		response = ResourceResponse.getResponse(HttpResponseStatus.OK,data,responseHeaders);
		
		ChannelFuture f = ctx.channel().writeAndFlush(response);
		f.addListener(ChannelFutureListener.CLOSE);
		
		IOUtils.closeQuietly(input);
		IOUtils.closeQuietly(output);
	}

	/**
	 * 设置方法调用参数
	 * 
	 * @param ctx
	 * @param request
	 * @param bodyType
	 * @param parameters
	 * @param params
	 * @throws Exception
	 */
	private void setParams(HttpContext context, FullHttpRequest request, BodyType bodyType, Parameter[] paramType,
			Object[] paramValue) throws Exception {

		for (int i = 0; i < paramType.length; i++) {

			Parameter parameter = paramType[i];
			Class<?> type = parameter.getType();

			if (type.equals(HttpContext.class)) {
				paramValue[i] = context;
			}

			if (type.equals(HttpParam.class)) {
				paramValue[i] = getHttpParam(context.getContext(), request);
			}

			if (type.equals(HttpBody.class)) {
				paramValue[i] = getHttpBody(request, bodyType);
			}

			if (type.equals(HttpHeader.class)) {
				paramValue[i] = getHttpHeader(request);
			}

			if (type.equals(HttpForm.class)) {
				paramValue[i] = getHttpForm(request);
			}

		}
	}

	/**
	 * 获得调用对象
	 * 
	 * @param ctx
	 * @param request
	 * @return
	 */
	private AccessTarget getTarget(ChannelHandlerContext ctx, FullHttpRequest request, String uri) {
		String httpMethod = request.method().name();
		return MappingFactory.getAccessTarget(httpMethod + Constants.UNDER_LINE + uri);
	}

	/**
	 * 输出数据
	 * @throws Exception 
	 */
	private void ifReturnValueThenOutput(HttpContext context, BodyType bodyType, AccessResult result)
			throws Exception {
		
		FullHttpResponse response = CommonResponse.getResponse(HttpResponseStatus.OK);
		//执行doAfter
		if(FilterFactory.filterable()&&!FilterFactory.doAfter(context, result)) {
			if (context.getContext().channel().isOpen()) {
				response = CommonResponse.getResponse(HttpResponseStatus.SERVICE_UNAVAILABLE);
				ChannelFuture f = context.getContext().channel().writeAndFlush(response);
				f.addListener(ChannelFutureListener.CLOSE);
			}
			return;
		}

		if (result != null) {
			if (bodyType == BodyType.JSON) {
				response = JsonResponse.getResponse(HttpResponseStatus.OK, result.getResult());
			} else if (bodyType == BodyType.XML) {
				response = XmlResponse.getResponse(HttpResponseStatus.OK, result.getResult());
			}
		}
		
		//Keep-Alive
		//response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
		//response.headers().set(HttpHeaderNames.CONTENT_LENGTH, res.content().readableBytes());
		//正常业务输出
		ChannelFuture f = context.getContext().channel().writeAndFlush(response);
		f.addListener(ChannelFutureListener.CLOSE);
	}

	/**
	 * 如果链接没有关闭，则执行关闭
	 */
	private void ifIsOpenThenClose(ChannelHandlerContext ctx) {
		if (ctx.channel().isOpen()) {
			FullHttpResponse response = 
					CommonResponse.getResponse(HttpResponseStatus.OK);
			ChannelFuture f = ctx.channel().writeAndFlush(response);
			f.addListener(ChannelFutureListener.CLOSE);
		}
	}

	/**
	 * 异常处理
	 */
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		cause.printStackTrace();
		FullHttpResponse response = 
				CommonResponse.getResponse(HttpResponseStatus.INTERNAL_SERVER_ERROR,cause.getMessage());
		ChannelFuture f = ctx.channel().writeAndFlush(response);
		f.addListener(ChannelFutureListener.CLOSE);
	}
}
